package org.example.gateway.service.impl;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.google.common.collect.Lists;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.example.gateway.bean.App;
import org.example.gateway.bean.AppInstance;
import org.example.gateway.bean.AppPlugin;
import org.example.gateway.bean.Plugin;
import org.example.gateway.enums.EnableEnum;
import org.example.gateway.exceptions.GatewayException;
import org.example.gateway.mapper.AppInstanceMapper;
import org.example.gateway.mapper.AppMapper;
import org.example.gateway.mapper.AppPluginMapper;
import org.example.gateway.mapper.PluginMapper;
import org.example.gateway.pojo.AppPluginDto;
import org.example.gateway.pojo.ChangeStatusDto;
import org.example.gateway.pojo.dto.AppInfoDto;
import org.example.gateway.pojo.dto.RegisterAppDto;
import org.example.gateway.pojo.dto.ServiceInstance;
import org.example.gateway.pojo.dto.UnregisterDto;
import org.example.gateway.pojo.vo.AppVo;
import org.example.gateway.service.AppService;
import org.example.gateway.transfer.AppInstanceTransfer;
import org.example.gateway.transfer.AppVoTransfer;
import org.example.gateway.utils.DateTimeUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.ExceptionHandler;

import javax.annotation.Resource;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
public class AppServiceImpl implements AppService {
    private static final Logger log = LoggerFactory.getLogger(AppServiceImpl.class);

    @Resource
    private AppMapper appMapper;
    @Resource
    private AppInstanceMapper instanceMapper;
    @Resource
    private AppPluginMapper appPluginMapper;
    @Resource
    private PluginMapper pluginMapper;

    Gson gson = new GsonBuilder().create();

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void register(RegisterAppDto registerAppDto) {
        App app = queryByAppName(registerAppDto.getAppName());
        Integer appId;
        if (app == null) {
            appId = addApp(registerAppDto);
        } else {
            appId = app.getId();
        }
        AppInstance instance = query(appId, registerAppDto.getIp(), registerAppDto.getPort(), registerAppDto.getVersion());
        if (instance == null) {
            AppInstance appInstance = new AppInstance();
            appInstance.setAppId(appId);
            appInstance.setIp(registerAppDto.getIp());
            appInstance.setPort(registerAppDto.getPort());
            appInstance.setVersion(registerAppDto.getVersion());
            appInstance.setCreatedTime(DateTimeUtil.LocalDateTime2Date(LocalDateTime.now()));
            instanceMapper.insert(appInstance);
        }
        log.info("register app success,dto:[{}]", gson.toJson(registerAppDto));
    }

    @Override
    public void unregister(UnregisterDto unregisterDto) {
        App app = queryByAppName(unregisterDto.getAppName());
        if (app == null) {
            return;
        }
        QueryWrapper<AppInstance> wrapper = new QueryWrapper<>();
        wrapper.lambda().eq(AppInstance::getAppId, app.getId())
                .eq(AppInstance::getVersion, unregisterDto.getVersion())
                .eq(AppInstance::getIp, unregisterDto.getIp())
                .eq(AppInstance::getPort, unregisterDto.getPort());
        instanceMapper.delete(wrapper);
        log.info("unregister app instance success,dto:[{}]", gson.toJson(unregisterDto));
    }

    @Override
    public List<AppInfoDto> getAppInfos(List<String> appNames) {
        List<App> apps = getAppList(appNames);
        List<Integer> appIdList = apps.stream().map(App::getId).collect(Collectors.toList());
        QueryWrapper<AppInstance> wrapper = new QueryWrapper<>();
        wrapper.lambda().in(AppInstance::getAppId, appIdList);
        List<AppInstance> instances = instanceMapper.selectList(wrapper);
        List<AppPluginDto> appPlugins = appPluginMapper.queryEnabledPlugins(appIdList);
        if (CollectionUtils.isEmpty(instances) || CollectionUtils.isEmpty(appPlugins)) {
            log.info("no app infos ...");
            return Lists.newArrayList();
        }
        return buildAppInfos(apps, instances, appPlugins);
    }

    @Override
    public List<AppVo> getList() {
        List<App> apps = appMapper.selectList(new QueryWrapper<>());
        if (CollectionUtils.isEmpty(apps)) {
            return Lists.newArrayList();
        }
        List<AppVo> appVos = AppVoTransfer.INSTANCE.mapToVOList(apps);
        List<Integer> appIds = appVos.stream().map(AppVo::getId).collect(Collectors.toList());
        QueryWrapper<AppInstance> query = Wrappers.query();
        query.lambda().in(AppInstance::getAppId, appIds);
        List<AppInstance> instances = instanceMapper.selectList(query);
        if (CollectionUtils.isEmpty(instances)) {
            appVos.forEach(appVo -> appVo.setInstanceNum(0));
        } else {
            Map<Integer, List<AppInstance>> map = instances.stream().collect(Collectors.groupingBy(AppInstance::getAppId));
            appVos.forEach(appVO -> appVO.setInstanceNum(map.getOrDefault(appVO.getId(), Lists.newArrayList()).size()));
        }
        return appVos;
    }

    @Override
    public void updateEnabled(ChangeStatusDto statusDto) {
        App app = new App();
        app.setId(statusDto.getId());
        app.setEnabled(statusDto.getEnabled());
        appMapper.updateById(app);
    }

    @Override
    public void delete(Integer id) {
        QueryWrapper<AppInstance> wrapper = new QueryWrapper<>();
        wrapper.lambda().eq(AppInstance::getAppId,id);
        List<AppInstance> instances = instanceMapper.selectList(wrapper);
        if (!CollectionUtils.isEmpty(instances)){
            throw new GatewayException("该服务存在实例不可删除！");
        }
        appMapper.deleteById(id);
    }

    private AppInstance query(Integer appId, String ip, Integer port, String version) {
        QueryWrapper<AppInstance> wrapper = new QueryWrapper<>();
        wrapper.lambda().eq(AppInstance::getAppId, appId)
                .eq(AppInstance::getIp, ip)
                .eq(AppInstance::getPort, port)
                .eq(AppInstance::getVersion, version);
        return instanceMapper.selectOne(wrapper);
    }

    private Integer addApp(RegisterAppDto registerAppDto) {
        App app = new App();
        BeanUtils.copyProperties(registerAppDto, app);
        app.setEnabled(EnableEnum.ENABLE.getCode());
        app.setCreatedTime(DateTimeUtil.LocalDateTime2Date(LocalDateTime.now()));
        appMapper.insert(app);
        bindPlugins(app);
        return app.getId();
    }

    private void bindPlugins(App app) {
        List<Plugin> plugins = pluginMapper.selectList(new QueryWrapper<>());
        if (CollectionUtils.isEmpty(plugins)) {
            throw new GatewayException("must init plugins first!");
        }
        plugins.forEach(plugin -> {
            AppPlugin ap = new AppPlugin();
            ap.setAppId(app.getId());
            ap.setPluginId(plugin.getId());
            ap.setEnabled(EnableEnum.ENABLE.getCode());
            pluginMapper.insert(plugin);
        });
    }

    private App queryByAppName(String appName) {
        QueryWrapper<App> wrapper = new QueryWrapper<>();
        wrapper.lambda().eq(App::getAppName, appName);
        App app = appMapper.selectOne(wrapper);
        return app;
    }

    private List<App> getAppList(List<String> appNames) {
        QueryWrapper<App> wrapper = new QueryWrapper<>();
        wrapper.lambda().in(App::getAppName, appNames);
        return appMapper.selectList(wrapper);
    }

    private List<AppInfoDto> buildAppInfos(List<App> apps, List<AppInstance> instances, List<AppPluginDto> appPlugins) {
        List<AppInfoDto> appInfos = new ArrayList<>();
        apps.forEach(app -> {
            AppInfoDto dto = new AppInfoDto();
            dto.setAppId(app.getId());
            dto.setAppName(app.getAppName());
            dto.setEnabled(app.getEnabled());
            List<String> enablePlugins = appPlugins.stream()
                    .filter(plugin -> plugin.getAppId().equals(app.getId()))
                    .map(AppPluginDto::getCode)
                    .collect(Collectors.toList());
            dto.setEnabledPlugins(enablePlugins);
            List<AppInstance> appInstances = instances.stream()
                    .filter(instance -> instance.getAppId().equals(app.getId()))
                    .collect(Collectors.toList());
            List<ServiceInstance> serviceInstances = AppInstanceTransfer.INSTANCE.mapToServiceList(appInstances);
            dto.setInstances(serviceInstances);
            appInfos.add(dto);
        });
        return appInfos;
    }
}
